package server

import (
	"context"
	"fmt"
	"io"
	"net"
	"os"

	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
	"wowCoupon.com/logger"
	"wowCoupon.com/pkg/auth"
	"wowCoupon.com/pkg/e"
	"wowCoupon.com/pkg/tools"
	pb "wowCoupon.com/rpcserver/business/ber"
	"wowCoupon.com/service/couponMetaService"
	"wowCoupon.com/service/userService"
	"wowCoupon.com/setting"
)

func Serve() {
	//Listen the port
	hostname, _ := os.Hostname()
	fmt.Println(hostname)

	lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", hostname, setting.RpcServerBusinessSetting.Port))
	if err != nil {
		logger.Fatal("rpc business server listener error: %s", err.Error())
		panic(err)
	}

	// Config TLS cert
	//creds, err := credentials.NewServerTLSFromFile("cert/server.crt", "cert/server.key")
	//if err != nil {
	//logger.Fatal("rpc business server config TLS error: %s", err.Error())
	//log.Panic(err)
	//}

	//Define rpc server
	s := grpc.NewServer(
		grpc.UnaryInterceptor(JwtUnaryServerInterceptor),
		//grpc.Creds(creds),
	)

	pb.RegisterBerServer(s, &businessServer{})

	//Run the rpc server with net.Listener
	serveWapper := func() error {
		logger.Info("rpc business server running on: %s", fmt.Sprintf("%s:%d", hostname, setting.RpcServerBusinessSetting.Port))
		if err := s.Serve(lis); err != nil {
			return err
		}
		return nil
	}

	if err := serveWapper(); err != nil {
		logger.Fatal("rpc business server grpc server error: %s", err.Error())
		panic(err)
	}
}

// JwtUnaryServerInterceptor server side unary interceptor for jwt token authentication
func JwtUnaryServerInterceptor(
	ctx context.Context,
	req interface{},
	info *grpc.UnaryServerInfo,
	handler grpc.UnaryHandler) (interface{}, error) {

	// Pre-processing logic
	// get method type from request context
	methodType, err := getMethodFromContext(ctx)
	if err != nil {
		logger.Error("rpc business server JwtUnaryServerInterceptor() error: %s", err.Error())
		return nil, err
	}

	if methodType == "Login" {
		// Invoking the handler to complete the normal execution of a unary RPC.
		m, err := handler(ctx, req)
		return m, err
	}

	// get token from request context
	tokenString, err := getTokenFromContext(ctx)
	if err != nil {
		logger.Error("rpc business server authentication error: %s", err.Error())
		return nil, err
	}

	// verify token
	if verified, err := auth.CheckToken(tokenString); !verified {
		logger.Error("rpc business server authentication error: invalid token; %s", err.Error())
		return nil, err
	}

	// Invoking the handler to complete the normal execution of a unary RPC.
	m, err := handler(ctx, req)
	return m, err
}

type businessServer struct {
	pb.UnimplementedBerServer
}

func (b *businessServer) Login(ctx context.Context, in *pb.LoginRequest) (*pb.LoginReply, error) {
	var user userService.User = userService.User{
		Username: in.Username,
		Password: in.Password,
	}

	// Credential successfully
	if user.VerifyCredentialInfo() {
		token := auth.CreateToken(in.Username)
		return &pb.LoginReply{
			Code:  200,
			Msg:   "OK",
			Token: token,
		}, nil
	}

	// Credential fail
	return &pb.LoginReply{
		Code:  403,
		Msg:   "Fobbiden, username or password is not correct",
		Token: "",
	}, nil
}

func (b *businessServer) GetCouponMetas(in *pb.GetCouponMetaRequest, stream pb.Ber_GetCouponMetasServer) error {
	// process
	conditions := in.GetConditions()
	c := &couponMetaService.CouponMeta{StringConditions: conditions}
	results, _ := c.GetByStringConditions()

	var reply *pb.GetCouponMetaReply
	for _, result := range results {
		reply = &pb.GetCouponMetaReply{
			Code: 200, Msg: "OK",
			BusinessId:     result.BusinessId,
			GoodId:         result.GoodId,
			Type:           result.Type,
			TypeMsg:        e.GetMsgCouponType(result.Type),
			Title:          result.Title,
			Amount:         result.Amount,
			RemainAmount:   result.RemainAmount,
			Comments:       result.Comments,
			IssueStartTime: tools.StampToString(result.IssueStartTime),
			IssueEndTime:   tools.StampToString(result.IssueEndTime),
			ValidStartTime: tools.StampToString(result.ValidStartTime),
			ValidEndTime:   tools.StampToString(result.ValidEndTime),
			Status:         result.Status,
			StatusMsg:      e.GetMsgCouponStatus(result.Status),
			CreatedBy:      result.CreatedBy,
			EditedBy:       result.EditedBy,
		}

		if err := stream.Send(reply); err != nil {
			return err
		}
	}

	return nil
}

func (b *businessServer) AddCouponMeta(ctx context.Context, in *pb.CouponMetaRequest) (*pb.CouponMetaReply, error) {
	IssueStartTime, _ := tools.StringToStamp(in.GetIssueStartTime())
	IssueEndTime, _ := tools.StringToStamp(in.GetIssueEndTime())
	ValidStartTime, _ := tools.StringToStamp(in.GetValidStartTime())
	ValidEndTime, _ := tools.StringToStamp(in.GetValidEndTime())

	addingCouponMeta := &couponMetaService.CouponMeta{
		BusinessId:     in.GetBusinessId(),
		GoodId:         in.GetGoodId(),
		Type:           in.GetType(),
		Title:          in.GetTitle(),
		Amount:         in.GetAmount(),
		RemainAmount:   in.GetAmount(),
		Comments:       in.GetComments(),
		IssueStartTime: IssueStartTime,
		IssueEndTime:   IssueEndTime,
		ValidStartTime: ValidStartTime,
		ValidEndTime:   ValidEndTime,
		Status:         e.COUPON_STATUS_NOT_START,
		CreatedBy:      "haha",
		EditedBy:       "haha",
	}

	if err := addingCouponMeta.Add(); err != nil {
		return &pb.CouponMetaReply{Code: e.ERROR_ADD_COUPON_META_FAIL, Msg: e.GetMsg(e.ERROR_ADD_COUPON_META_FAIL)}, err
	}

	return &pb.CouponMetaReply{Code: e.SUCCESS, Msg: e.GetMsg(e.SUCCESS)}, nil

}

func (b *businessServer) AddCouponMetas(stream pb.Ber_AddCouponMetasServer) error {
	for {
		in, err := stream.Recv()
		if err == io.EOF {
			return stream.SendAndClose(&pb.CouponMetaReply{Code: 200, Msg: "OK"})
		}

		out, err := b.AddCouponMeta(context.Background(), in)
		if err != nil || out.GetCode() != 200 {
			return stream.SendAndClose(&pb.CouponMetaReply{Code: 500, Msg: "Error"})
		}
	}
}

// get token from request context
func getTokenFromContext(ctx context.Context) (string, error) {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return "", fmt.Errorf("ErrNoMetadataInContext")
	}
	// md 的类型是 type MD map[string][]string
	token, ok := md["authorization"]
	if !ok || len(token) == 0 {
		return "", fmt.Errorf("ErrNoAuthorizationInMetadata")
	}
	// 因此，token 是一个字符串数组，我们只用了 token[0]
	return token[0], nil
}

// get token from request context
func getMethodFromContext(ctx context.Context) (string, error) {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return "", fmt.Errorf("ErrNoMetadataInContext")
	}
	// md 的类型是 type MD map[string][]string
	method, ok := md["methodtype"]
	if !ok || len(method) == 0 {
		return "", nil
	}
	// method 是一个字符串数组，我们只用了 method[0]
	return method[0], nil
}
